package info.batcloud.fanli.core.service.impl;

import com.ctospace.archit.common.pagination.Paging;
import info.batcloud.fanli.core.constants.MessageKeyConstants;
import info.batcloud.fanli.core.context.StaticContext;
import info.batcloud.fanli.core.domain.Result;
import info.batcloud.fanli.core.dto.UserWithdrawDTO;
import info.batcloud.fanli.core.entity.User;
import info.batcloud.fanli.core.entity.UserWithdraw;
import info.batcloud.fanli.core.entity.Wallet;
import info.batcloud.fanli.core.enums.FundTransferType;
import info.batcloud.fanli.core.enums.UserWithdrawStatus;
import info.batcloud.fanli.core.enums.WalletFlowDetailType;
import info.batcloud.fanli.core.exception.BizException;
import info.batcloud.fanli.core.helper.PagingHelper;
import info.batcloud.fanli.core.repository.UserRepository;
import info.batcloud.fanli.core.repository.UserWithdrawRepository;
import info.batcloud.fanli.core.repository.WalletRepository;
import info.batcloud.fanli.core.service.*;
import info.batcloud.fanli.core.settings.WithdrawSetting;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

@Service
public class UserWithdrawServiceImpl implements UserWithdrawService {

    @Inject
    private UserWithdrawRepository userWithdrawRepository;

    @Inject
    private UserRepository userRepository;

    @Inject
    private WalletService walletService;

    @Inject
    private SmsService smsService;

    @Inject
    private WalletRepository walletRepository;

    @Inject
    private SystemSettingService systemSettingService;

    @Inject
    private FundTransferService fundTransferService;

    @Override
    @Transactional
    public synchronized void withdraw(long userId, UserWithDrawParams params) {
        Wallet wallet = walletRepository.findByUserId(userId);
        //判断账户余额够不够提现的金额
        if (params.getMoney() < 0 || wallet.getMoney() < params.getMoney()) {
            throw new BizException(MessageKeyConstants.NO_ENOUGH_MONEY_FOR_WITHDRAW);
        }
        WithdrawSetting withdrawSetting = systemSettingService.findActiveSetting(WithdrawSetting.class);
        if (params.getMoney() < withdrawSetting.getMinWithdrawValue()) {
            throw new BizException(MessageKeyConstants.LESS_MONEY_FOR_WITHDRAW, new Object[]{withdrawSetting.getMinWithdrawValue()});
        }
        User user = userRepository.findOne(userId);
        if (!smsService.validatePhoneCode(user.getPhone(), params.getVerifyCode())) {
            throw new BizException(MessageKeyConstants.PHONE_VERIFY_CODE_INVALID);
        }
        smsService.unlockPhone(user.getPhone());
        WalletService.WalletChangeResult result = walletService.addMoney(userId, -params.getMoney(), WalletFlowDetailType.USER_WITHDRAW, "");
        UserWithdraw userWithdraw = new UserWithdraw();
        userWithdraw.setUser(userRepository.findOne(userId));
        userWithdraw.setCreateTime(new Date());
        userWithdraw.setMoney(params.getMoney());
        if (withdrawSetting.isAutoVerify()) {
            userWithdraw.setStatus(UserWithdrawStatus.WAIT_TRANSFER);
        } else {
            userWithdraw.setStatus(UserWithdrawStatus.WAIT_VERIFY);
        }
        userWithdraw.setUpdateTime(userWithdraw.getCreateTime());
        userWithdraw.setWalletFlowDetailId(result.getWalletDetailId());
        userWithdraw.setPayeeAccount(params.getPayeeAccount());
        userWithdraw.setPayeeName(params.getPayeeName());
        userWithdrawRepository.save(userWithdraw);
        wallet.setWithdrawMoney(wallet.getWithdrawMoney() + params.getMoney());
        walletRepository.save(wallet);
    }

    @Override
    public Paging<UserWithdrawDTO> search(SearchParam param) {
        Specification<UserWithdraw> specification = (root, query, cb) -> {
            Predicate predicate = cb.conjunction();
            List<Expression<Boolean>> expressions = predicate.getExpressions();
            if (StringUtils.isNotBlank(param.getUserPhone())) {
                expressions.add(cb.equal(root.get("user").get("phone"), param.getUserPhone()));
            }

            if (param.getCreateStartTime() != null) {
                expressions.add(cb.greaterThanOrEqualTo(root.get("createTime"), param.getCreateStartTime()));
            }

            if (param.getCreateEndTime() != null) {
                expressions.add(cb.lessThanOrEqualTo(root.get("endTime"), param.getCreateEndTime()));
            }

            if (param.getCreateStartDate() != null) {
                expressions.add(cb.greaterThanOrEqualTo(root.get("createTime"), DateUtils.truncate(param.getCreateStartDate(), Calendar.DATE)));
            }

            if (param.getCreateEndDate() != null) {
                expressions.add(cb.lessThan(root.get("createTime"),
                        DateUtils.truncate(DateUtils.addDays(param.getCreateEndDate(), 1), Calendar.DATE)));
            }

            if (param.getCreateEndTime() != null) {
                expressions.add(cb.lessThanOrEqualTo(root.get("endTime"), param.getCreateEndTime()));
            }

            if (param.getStatus() != null) {
                expressions.add(cb.equal(root.get("status"), param.getStatus()));
            }
            return predicate;
        };
        Sort sort = new Sort(Sort.Direction.DESC, "updateTime");
        Pageable pageable = new PageRequest(param.getPage() - 1,
                param.getPageSize(), sort);
        Page<UserWithdraw> page = userWithdrawRepository.findAll(specification, pageable);
        return PagingHelper.of(page, item -> toBo(item), param.getPage(), param.getPageSize());
    }

    @Override
    public UserWithdrawDTO findById(long id) {
        UserWithdraw userWithdraw = userWithdrawRepository.findOne(id);
        return toBo(userWithdraw);
    }

    private UserWithdrawDTO toBo(UserWithdraw userWithdraw) {
        UserWithdrawDTO bo = new UserWithdrawDTO();
        BeanUtils.copyProperties(userWithdraw, bo);
        bo.setUserId(userWithdraw.getUser().getId());
        bo.setUserPhone(userWithdraw.getUser().getPhone());
        bo.setUserNickname(userWithdraw.getUser().getNickname());
        return bo;
    }

    @Override
    public synchronized Result withdrawFundTransferById(long id) {
        Result result = new Result();
        UserWithdraw userWithdraw = userWithdrawRepository.findOne(id);
        if (checkCanTransfer(userWithdraw.getStatus())) {
            result.setCode("该状态不允许转账");
            return result;
        }
        userWithdraw.setStatus(UserWithdrawStatus.ON_TRANSFER);
        userWithdrawRepository.save(userWithdraw);
        FundTransferService.FundTransferParam transferParam = new FundTransferService.FundTransferParam();
        transferParam.setAmount(userWithdraw.getMoney());
        transferParam.setPayeeAccount(userWithdraw.getPayeeAccount());
        transferParam.setPayeeRealName(userWithdraw.getPayeeName());
        transferParam.setRemark(StaticContext.messageSource.getMessage(MessageKeyConstants.USER_WITHDRAW_REMARK, null, "", null));
        FundTransferService.FundTransferResult transferResult = fundTransferService.transferFund(transferParam);
        userWithdraw.setStatus(transferResult.isSuccess() ? UserWithdrawStatus.TRANSFER_SUCCESS : UserWithdrawStatus.TRANSFER_FAIL);
        userWithdraw.setRemark(transferResult.getErrorMsg());
        userWithdraw.setFundTransferOrderId(transferResult.getFundTransferOrderId());
        userWithdraw.setPayDate(transferResult.getPayDate());
        userWithdraw.setAlipayOrderId(transferResult.getAlipayOrderId());
        userWithdrawRepository.save(userWithdraw);
        result.setSuccess(transferResult.isSuccess());
        result.setCode(transferResult.getErrorMsg());
        return result;
    }

    @Override
    public String findUserLastAlipayAccount(long userId) {
        UserWithdraw userWithdraw = userWithdrawRepository.findTopByUserIdOrderByIdDesc(userId);
        return userWithdraw == null ? null : userWithdraw.getPayeeAccount();
    }

    @Override
    public Result withdrawManualFundTransfer(ManualFundTransferParam param) {
        Result result = new Result();
        UserWithdraw userWithdraw = userWithdrawRepository.findOne(param.getId());
        if (checkCanTransfer(userWithdraw.getStatus())) {
            result.setCode("该状态不允许转账");
            return result;
        }
        userWithdraw.setFundTransferType(FundTransferType.MANUAL);
        userWithdraw.setStatus(UserWithdrawStatus.TRANSFER_SUCCESS);
        userWithdraw.setPayDate(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        userWithdraw.setAlipayOrderId(param.getAlipayOrderId());
        userWithdrawRepository.save(userWithdraw);
        result.setSuccess(true);
        return result;
    }

    private boolean checkCanTransfer(UserWithdrawStatus status) {
        return status != UserWithdrawStatus.WAIT_TRANSFER
                && status != UserWithdrawStatus.RETRY_TRANSFER
                && status != UserWithdrawStatus.TRANSFER_FAIL;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void verify(VerifyParam param) {
        UserWithdraw userWithdraw = userWithdrawRepository.findOne(param.getId());
        if (userWithdraw.getStatus() != UserWithdrawStatus.WAIT_VERIFY
                && userWithdraw.getStatus() != UserWithdrawStatus.WAIT_TRANSFER) {
            throw new BizException("该提现记录不支持审核");
        }
        if (param.isSuccess()) {
            userWithdraw.setStatus(UserWithdrawStatus.WAIT_TRANSFER);
        } else {
            userWithdraw.setStatus(UserWithdrawStatus.VERIFY_FAIL);
            walletService.addMoney(userWithdraw.getUser().getId(), userWithdraw.getMoney(), WalletFlowDetailType.USER_WITHDRAW_VERIFY_FAIL_RETURN, userWithdraw.getId() + "");
        }
        userWithdraw.setRemark(param.getRemark());
        userWithdrawRepository.save(userWithdraw);
    }

    private static String[] TITLE_LIST = new String[]{
            "会员ID",
            "用户昵称",
            "手机号",
            "提现时间",
            "提现金额",
            "收款姓名",
            "支付宝交易号",
            "转账时间",
            "备注"
    };

    @Inject
    private TmpFileService tmpFileService;

    @Override
    public File exportXsl(ExportParam param) throws IOException {
        File excelFile = tmpFileService.createFile("userwithdraw_" + System.currentTimeMillis() + ".xls");
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet();
        sheet.autoSizeColumn(1, true);
        HSSFFont boldFond = workbook.createFont();
        boldFond.setBold(true);
        boldFond.setFontHeightInPoints((short) 16);
        CellStyle foldStyle = workbook.createCellStyle();
        CellStyle commonStyle = workbook.createCellStyle();
        HSSFFont commonFont = workbook.createFont();
        commonFont.setFontHeightInPoints((short) 14);
        commonStyle.setFont(commonFont);
        foldStyle.setFont(boldFond);
        HSSFRow titleRow = sheet.createRow(0);
        for (int i = 0; i < TITLE_LIST.length; i++) {
            HSSFCell titleCell = titleRow.createCell(i);
            //给单元格设置内容
            titleCell.setCellValue(TITLE_LIST[i]);
            titleCell.setCellStyle(foldStyle);
        }
        int page = 1;
        int pageSize = param.getMaxCount() < 100 ? param.getMaxCount() : 100;
        int maxPage = (param.getMaxCount() + pageSize - 1) / pageSize;
        int rowIndex = 1;
        param.setPageSize(pageSize);
        while (true) {
            param.setPage(page);
            Paging<UserWithdrawDTO> paging = this.search(param);
            for (UserWithdrawDTO dto : paging.getResults()) {
                HSSFRow row = sheet.createRow(rowIndex++);
                row.setHeightInPoints(20);
                for (int i = 0; i < TITLE_LIST.length; i++) {
                    HSSFCell rowCell = row.createCell(i);
                    rowCell.setCellStyle(commonStyle);
                    switch (TITLE_LIST[i]) {
                        case "会员ID":
                            rowCell.setCellValue(dto.getUserId());
                            break;
                        case "手机号":
                            rowCell.setCellValue(dto.getUserPhone());
                            break;
                        case "用户昵称":
                            rowCell.setCellValue(dto.getUserNickname());
                            break;
                        case "提现时间":
                            rowCell.setCellValue(DateFormatUtils.format(dto.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
                            break;
                        case "提现金额":
                            rowCell.setCellType(CellType.NUMERIC);
                            rowCell.setCellValue(String.valueOf(String.format("%.2f", dto.getMoney())));
                            break;
                        case "收款姓名":
                            rowCell.setCellValue(dto.getPayeeName());
                            break;
                        case "支付宝交易号":
                            rowCell.setCellValue(dto.getAlipayOrderId());
                            break;
                        case "转账时间":
                            if (dto.getUpdateTime() != null) {
                                rowCell.setCellValue(dto.getPayDate());
                            }
                            break;
                        case "备注":
                            rowCell.setCellValue(dto.getRemark());
                            break;
                    }
                }
            }
            if (!paging.getHasNext()) {
                break;
            }
            if (page >= maxPage) {
                break;
            }
            page++;
        }
        for (int i = 0; i < TITLE_LIST.length; i++) {
            sheet.autoSizeColumn(i);
        }
        workbook.write(excelFile);
        return excelFile;
    }
}
